#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File    :   applicationAvailabilityCalculation.py
@Contact :   zhujuxing@foxmail.com
@License :   (C)Copyright 2021-2022

@Modify Time      @Author    @Version    @Description
------------      -------    --------    -----------
2021/6/16 12:53   zhujuxing      1.0         None
"""
import os
from collections import Counter
from multiprocessing import Pool

import numpy as np

from cloudVirtrualizedNetworkApplicationReliability.src.networkEvolutionObjectModel import *
from cloudVirtrualizedNetworkApplicationReliability.src.networkEvolutionRulesAnalysis import *


def app_ava_cal(file, t, n, control_plane_type, file_name):
    single_app_avail = pd.DataFrame(columns=[i + 1 for i in range(n)])
    for i in range(n):
        app_ava_cal_single(file, i, single_app_avail, t, control_plane_type)

    single_app_avail['result'] = single_app_avail.apply(np.mean, axis=1)
    single_app_avail.to_excel(file_name)
    print('单业务可用度计算结果为：' + os.linesep)
    print(single_app_avail)

    whole_app_avail = np.mean(single_app_avail['result'].to_list())
    print('整网业务可用度计算结果为：%f' % whole_app_avail)
    return single_app_avail, whole_app_avail


def app_ava_cal_single(file, i, single_app_avail, t, control_plane_type):
    print("第%d次计算开始\n" % i)
    network = CloudVirtualizedNetwork(file,
                                      control_plane_type=control_plane_type)
    evolution_condition = network.evol(t)
    network = network_evolution_rules_analysis_for_evolution_condition(network,
                                                                       evolution_condition,
                                                                       False)
    single_app_avail[i + 1] = network.application_info["Availability"]
    res = network.application_info["Availability"]
    print("第%d次计算结束，计算结果为\n%s" % (i, res))
    return res, i


def multi_process_avail_cal(input_file_path, t, n, pn, control_plane_type, out_put_file_path):
    """
    业务可用度多进程计算函数
    :param input_file_path: 网络参数路径
    :param t: 计算周期
    :param n: 计算次数
    :param pn: 进程池代销
    :param control_plane_type: ControlPlaneType的枚举类型，有CENTRALIZATION/DISTRIBUTION/NONE三种取值。
    :param out_put_file_path: 保存的计算结果路径
    :return: 业务可用度计算结果
    """
    nil = check_input_file(input_file_path)
    if not nil:
        raise Exception("请检查输入的参数文档设置。")

    pool = Pool(processes=pn)
    single_app_avail = pd.DataFrame(columns=[i + 1 for i in range(n)])

    def fun(res):
        nonlocal single_app_avail
        i = res[1]
        single_app_avail[i + 1] = res[0]

    for i in range(n):
        pool.apply_async(app_ava_cal_single, args=(input_file_path, i, single_app_avail, t, control_plane_type),
                         callback=fun)

    pool.close()
    pool.join()

    single_app_avail.loc["total"] = single_app_avail.apply(np.mean)
    single_app_avail["result"] = single_app_avail.apply(np.mean, axis=1)
    single_app_avail.to_excel(out_put_file_path)
    return single_app_avail


def check_input_file(input_file_path):
    nil = True
    node_info = pd.read_excel(input_file_path, sheet_name='node_info')
    edge_info = pd.read_excel(input_file_path, sheet_name='edge_info')
    fail_info = pd.read_excel(input_file_path, sheet_name='fail_info')
    vnf_info = pd.read_excel(input_file_path, sheet_name='vnf_info')
    application_info = pd.read_excel(input_file_path, sheet_name=' application_info')

    vnf_info['工作节点'] = vnf_info['工作节点'].apply(lambda x_str: x_str.split(','))
    application_info['业务逻辑路径'] = application_info['业务逻辑路径'].apply(lambda x_str: x_str.split(','))

    if len(set(node_info['节点ID'])) != len(node_info['节点ID']):
        print("错误，节点信息中节点ID不唯一！")
        nil = False

    if "DCGW" not in node_info['节点类型'].to_list():
        print("错误，缺少类型为DCGW的节点")
        nil = False

    if "UE" not in node_info['节点类型'].to_list():
        print("错误，缺少终端节点")
        nil = False

    if len(set(edge_info['链路ID'])) != len(edge_info['链路ID']):
        print("错误，链路信息中链路ID不唯一！")
        nil = False

    if not edge_info['源节点ID'].isin(node_info['节点ID']).all():
        err_edg_df = edge_info[edge_info['源节点ID'].apply(lambda x_str:
                                                        True if x_str not in node_info['节点ID'] else False)]
        err_edg_list = err_edg_df['链路ID'].to_list()
        print("错误, %s中的源节点ID未在节点信息中配置" % err_edg_list)
        nil = False

    if not edge_info['目的节点ID'].isin(node_info['节点ID']).all():
        err_edg_df = edge_info[edge_info['目的节点ID'].apply(lambda x_str:
                                                         True if x_str not in node_info['节点ID'] else False)]
        err_edg_list = err_edg_df['链路ID'].to_list()
        print("错误, %s中的目的节点ID未在节点信息中配置" % err_edg_list)
        nil = False

    if not fail_info['故障检测率'].apply(lambda x_float: True if ((x_float >= 0) & (x_float <= 1)) else False).all():
        err_fail_df = fail_info[fail_info['故障检测率'].apply(lambda x_float:
                                                         False if ((x_float >= 0) & (x_float <= 1)) else True)]
        err_fail_list = err_fail_df['故障模式']
        print("错误，%s故障模式的故障检测率不在[0,1]之间！" % err_fail_list)
        nil = False

    deploy_node_list_all = []
    for x in vnf_info.iterrows():
        deploy_node_list = x[1]['工作节点']
        deploy_node_list_all.extend(deploy_node_list)
        backup_node = x[1]['备用节点']
        if type(backup_node) == str:
            deploy_node_list_all.append(x[1]['备用节点'])
        for n in deploy_node_list:
            if n not in node_info['节点ID'].to_list():
                print("错误，%s上部署的节点%s未在节点信息中配置!" % (x[1]['VNF名称'], n))
                nil = False

        if x[1]['备份类型'] == "N way":
            vm_vs_dict = {}
            for n in deploy_node_list:
                vs_or_vm = edge_info[edge_info.目的节点ID == n]['源节点ID'].to_list()[0]
                vm_vs_dict[n] = vs_or_vm
            if len(vm_vs_dict.keys()) != len(set(vm_vs_dict.values())):
                print("错误，%s上的部署节点不能在同一个服务器或VM上" % x[1]['VNF名称'])
                nil = False
    deploy_node_count_dict = dict(Counter(deploy_node_list_all))
    for i in deploy_node_count_dict.keys():
        if deploy_node_count_dict[i] > 1:
            print("错误，%s上部署了多个VNF" % i)
            nil = False

    for x in application_info.iterrows():
        vnf_path = x[1]['业务逻辑路径']
        for vnf in vnf_path:
            if vnf not in vnf_info['VNF名称'].to_list():
                print("错误，%s上的%s未定义" % (x[1]['业务名称'], vnf))
                nil = False
    return nil
